#include <semaphore.h>
#include <wd.h>
#include <wd_bmm.h>

#include <pthread.h>
#include "kaev1_memory.h"

#define MAX_NODE 64

struct wd_queue *WD_NewQueue(int algType)
{
    struct wd_queue *queue = (struct wd_queue *)malloc(sizeof(struct wd_queue));
    if (queue == NULL) {
        return NULL;
    }

    memset(queue, 0, sizeof(struct wd_queue));

    switch (algType) {
        case WCRYPTO_RSA:
            queue->capa.alg = "rsa";
            break;
        case WCRYPTO_DH:
            queue->capa.alg = "dh";
            break;
        case WCRYPTO_CIPHER:
            queue->capa.alg = "cipher";
            break;
        case WCRYPTO_DIGEST:
            queue->capa.alg = "digest";
            break;
        case WCRYPTO_COMP:
        case WCRYPTO_EC:
        case WCRYPTO_RNG:
        default:
            free(queue);
            queue = NULL;
            return NULL;
    }

    int ret = wd_request_queue(queue);
    if (ret) {
        free(queue);
        queue = NULL;
        return NULL;
    }

    return queue;
}

void WD_FreeQueue(struct wd_queue *queue)
{
    if (queue != NULL) {
        wd_release_queue(queue);
        free(queue);
        queue = NULL;
    }
}

KaeQueuePoolHead *KaeInitQueuePool(int algType)
{
    KaeQueuePoolHead *kaePool = NULL;
    kaePool = (KaeQueuePoolHead *)malloc(sizeof(KaeQueuePoolHead));
    if (kaePool == NULL) {
        return NULL;
    }

    /* fill data of head */
    kaePool->algType = algType;
    kaePool->next = NULL;
    kaePool->poolUseNum = 0;

    /* malloc a pool */
    kaePool->kaeQueuePool = (KaeQueuePoolNode *)malloc(MAX_NODE * sizeof(KaeQueuePoolNode));
    if (kaePool->kaeQueuePool == NULL) {
        free(kaePool);
        return NULL;
    }

    memset(kaePool->kaeQueuePool, 0, MAX_NODE * sizeof(KaeQueuePoolNode));

    pthread_mutex_init(&kaePool->kaeQueueMutex, NULL);
    pthread_mutex_init(&kaePool->destroyMutex, NULL);

    return kaePool;
}

static KaeQueueDataEntry *KaeGetDataFromList(KaeQueuePoolHead *poolHead)
{
    int i = 0;

    KaeQueueDataEntry *queueDataNode = NULL;
    KaeQueuePoolHead *tempPool = poolHead;

    if ((poolHead->poolUseNum == 0) && (poolHead->next == NULL)) {
        return queueDataNode;
    }

    while (tempPool != NULL) {
        for (i = 0; i < tempPool->poolUseNum; i++) {
            if (tempPool->kaeQueuePool[i].entry == NULL) {
                continue;
            }

            if (KaeSpinLockTrylock(&(tempPool->kaeQueuePool[i].spinlock))) {
                if (tempPool->kaeQueuePool[i].entry == NULL) {
                    KaeSpinLockUnlock(&(tempPool->kaeQueuePool[i].spinlock));
                    continue;
                } else {
                    queueDataNode = tempPool->kaeQueuePool[i].entry;
                    tempPool->kaeQueuePool[i].entry = (KaeQueueDataEntry *)NULL;
                    KaeSpinLockUnlock(&(tempPool->kaeQueuePool[i].spinlock));
                    return queueDataNode;
                }
            }
        }
        /* next pool */
        tempPool = tempPool->next;
    }

    return queueDataNode;
}

static void KaeFreeWdQueueMemory(KaeQueueDataEntry *queueNode)
{
    if (queueNode->mempool != NULL) {
        wd_blkpool_destroy(queueNode->mempool);
        queueNode->mempool = NULL;
    }

    if (queueNode->queue != NULL) {
        WD_FreeQueue(queueNode->queue);
        queueNode->queue = NULL;
    }

    free(queueNode);
    queueNode = NULL;
}

struct wd_queue_mempool *CreateAlgWdQueueMemPool(int algType, struct wd_queue *q)
{
    struct wd_queue_mempool *mempool = NULL;
    unsigned int block_size;
    unsigned int block_num;

    switch (algType) {
        case WCRYPTO_RSA:
            block_size = RSA_BLOCK_SIZE;
            block_num = RSA_BLOCK_NUM;
            break;
        case WCRYPTO_DH:
            block_size = DH_BLOCK_SIZE;
            block_num = DH_BLOCK_NUM;
            break;
        case WCRYPTO_CIPHER:
            block_size = CIPHER_BLOCK_SIZE;
            block_num = CIPHER_BLOCK_NUM;
            break;
        case WCRYPTO_DIGEST:
            block_size = DIGEST_BLOCK_SIZE;
            block_num = DIGEST_BLOCK_NUM;
            break;
        case WCRYPTO_COMP:
        case WCRYPTO_EC:
        case WCRYPTO_RNG:
        default:
            return NULL;
    }

    struct wd_blkpool_setup setup;

    memset(&setup, 0, sizeof(setup));
    setup.block_size = block_size;
    setup.block_num = block_num;
    setup.align_size = 64; // align with 64

    mempool = (struct wd_queue_mempool *)wd_blkpool_create(q, &setup);
    return mempool;
}

static KaeQueueDataEntry *KaeNewWdQueueMemory(int algType)
{
    KaeQueueDataEntry *queueNode = NULL;

    queueNode = (KaeQueueDataEntry *)malloc(sizeof(KaeQueueDataEntry));
    if (queueNode == NULL) {
        return NULL;
    }
    memset(queueNode, 0, sizeof(KaeQueueDataEntry));

    queueNode->queue = WD_NewQueue(algType);
    if (queueNode->queue == NULL) {
        goto err;
    }

    queueNode->mempool = CreateAlgWdQueueMemPool(algType, queueNode->queue);
    if (queueNode->mempool == NULL) {
        goto err;
    }

    return queueNode;

err:
    KaeFreeWdQueueMemory(queueNode);
    return NULL;
}

KaeQueueDataEntry *KaeGetNodeFromPool(KaeQueuePoolHead *poolHead)
{
    KaeQueueDataEntry *queueDataNode = NULL;

    if (poolHead == NULL) {
        return NULL;
    }

    queueDataNode = KaeGetDataFromList(poolHead);
    if (queueDataNode == NULL) {
        queueDataNode = KaeNewWdQueueMemory(poolHead->algType);
    }

    return queueDataNode;
}

static void KaeSetPoolUseNum(KaeQueuePoolHead *pool, int setNum)
{
    pthread_mutex_lock(&pool->kaeQueueMutex);
    if (setNum > pool->poolUseNum) {
        pool->poolUseNum = setNum;
    }
    (void)pthread_mutex_unlock(&pool->kaeQueueMutex);
}

int KaePutNodeToPool(KaeQueuePoolHead *poolHead, KaeQueueDataEntry *nodeData)
{
    int i = 0;
    KaeQueuePoolHead *tempPool = poolHead;
    KaeQueuePoolHead *lastPool = NULL;

    if (nodeData == NULL || poolHead == NULL) {
        return 0;
    }

    while (tempPool != NULL) {
        for (i = 0; i < MAX_NODE; i++) {
            if (tempPool->kaeQueuePool[i].entry) {
                continue;
            }

            if (KaeSpinLockTrylock(&(tempPool->kaeQueuePool[i].spinlock))) {
                if (tempPool->kaeQueuePool[i].entry) {
                    KaeSpinLockUnlock(&(tempPool->kaeQueuePool[i].spinlock));
                    continue;
                } else {
                    tempPool->kaeQueuePool[i].entry = nodeData;
                    tempPool->kaeQueuePool[i].addTime = time((time_t *)NULL);
                    KaeSpinLockUnlock(&(tempPool->kaeQueuePool[i].spinlock));
                    if (i >= tempPool->poolUseNum) {
                        KaeSetPoolUseNum(tempPool, i + 1);
                    }
                    return 1;
                }
            }
        }
        lastPool = tempPool;
        tempPool = tempPool->next;
        /* if no empty pool to add,new a pool */
        if (tempPool == NULL) {
            pthread_mutex_lock(&lastPool->destroyMutex);
            if (lastPool->next == NULL) {
                tempPool = KaeInitQueuePool(lastPool->algType);
                if (tempPool == NULL) {
                    (void)pthread_mutex_unlock(&lastPool->destroyMutex);
                    break;
                }
                lastPool->next = tempPool;
            }
            (void)pthread_mutex_unlock(&lastPool->destroyMutex);
        }
    }
    /* if not added,free it */
    KaeFreeWdQueueMemory(nodeData);
    return 0;
}

void KaeQueuePoolReset(KaeQueuePoolHead *poolHead)
{
    (void)poolHead;
    return;
}

void KaeQueuePoolDestroy(KaeQueuePoolHead *poolHead)
{
    int error = 0;
    int i = 0;
    KaeQueueDataEntry *queueDataNode = (KaeQueueDataEntry *)NULL;
    KaeQueuePoolHead *tempPool = NULL;
    KaeQueuePoolHead *curPool = poolHead;

    while (curPool != NULL) {
        error = pthread_mutex_lock(&curPool->destroyMutex);
        if (error != 0) {
            (void)pthread_mutex_unlock(&curPool->destroyMutex);
            return;
        }

        error = pthread_mutex_lock(&curPool->kaeQueueMutex);
        if (error != 0) {
            (void)pthread_mutex_unlock(&curPool->destroyMutex);
            return;
        }
        for (i = 0; i < curPool->poolUseNum; i++) {
            queueDataNode = curPool->kaeQueuePool[i].entry;
            if (queueDataNode != NULL) {
                KaeFreeWdQueueMemory(queueDataNode);
                curPool->kaeQueuePool[i].entry = NULL;
            }
        }

        free(curPool->kaeQueuePool);

        (void)pthread_mutex_unlock(&curPool->kaeQueueMutex);
        (void)pthread_mutex_unlock(&curPool->destroyMutex);

        pthread_mutex_destroy(&curPool->kaeQueueMutex);
        pthread_mutex_destroy(&curPool->destroyMutex);

        tempPool = curPool->next;
        free(curPool);
        curPool = tempPool;
    }
    return;
}

void KaeQueuePoolCheckAndRelease(KaeQueuePoolHead *poolHead)
{
    int i = 0;
    int error;
    time_t current_time;
    KaeQueueDataEntry *queueDataNode = NULL;
    KaeQueuePoolHead *curPool = poolHead;

    current_time = time((time_t *)NULL);

    while (curPool != NULL) {
        error = pthread_mutex_lock(&curPool->destroyMutex);
        if (error != 0) {
            curPool = curPool->next;
            (void)pthread_mutex_unlock(&curPool->destroyMutex);
            continue;
        }
        if (curPool->kaeQueuePool == NULL) {
            (void)pthread_mutex_unlock(&curPool->destroyMutex);
            curPool = curPool->next;
            continue;
        }

        for (i = curPool->poolUseNum - 1; i >= 0; i--) {
            if (curPool->kaeQueuePool[i].entry == NULL) {
                continue;
            }

            if (difftime(current_time, curPool->kaeQueuePool[i].addTime) < 5) {
                continue;
            }

            if (KaeSpinLockTrylock(&(curPool->kaeQueuePool[i].spinlock))) {
                if ((curPool->kaeQueuePool[i].entry == NULL) ||
                    (difftime(current_time, curPool->kaeQueuePool[i].addTime) < 5)) {
                    KaeSpinLockUnlock(&(curPool->kaeQueuePool[i].spinlock));
                    continue;
                } else {
                    queueDataNode = curPool->kaeQueuePool[i].entry;
                    curPool->kaeQueuePool[i].entry = (KaeQueueDataEntry *)NULL;
                    KaeSpinLockUnlock(&(curPool->kaeQueuePool[i].spinlock));
                    KaeFreeWdQueueMemory(queueDataNode);
                }
            }
        }

        (void)pthread_mutex_unlock(&curPool->destroyMutex);
        curPool = curPool->next;
    }

    return;
}